JavaScript is a very forgiving language. It’s easy to write code that runs but has mistakes in it.
In this article, we’ll look at some best practices for importing and exporting from JavaScript modules.
Only Import From a Path in One Place
If we import multiple members from one module, then all members should be imported in one import
statement.
For instance, instead of writing the following code to import members:
module.js
export const foo = 1;
export const bar = 2;
index.js
import { foo } from "./module";
import { bar } from "./module";
console.log(foo, bar);
In the code above, we have 2 lines to import 2 different members from the same module. That’s no good since we have lots of duplicate code in one place.
Instead, we should just put them all in the same module to save space and remove duplicate code.
For example, we can instead write the following:
module.js
export const foo = 1;
export const bar = 2;
index.js
import { foo, bar } from "./module";
console.log(foo, bar);
In the code above, we imported the members of module.js
in index.js
all in one line. As we can see, we saved lots of typing and duplication since we combined the 2 lines into one.
Do Not Export Mutable Bindings
We shouldn’t export mutable entities because we don’t want to accidentally change them inside the module that it’s export from. For instance, if we have the following code:
module.js
export let foo = 1;
foo = 3;
export const bar = 2;
index.js
import { foo, bar } from "./module";
console.log(foo, bar);
In the code above, we exported the foo
member from module.js
and then change the value of foo
later.
Then when we import foo
in index.js
, we see that the value 3 is logged for foo
.
Therefore whenever we change the value by reassigning it, the new value will be exported.
Therefore, we shouldn’t export members that have been defined with let
since these changes may be hidden if we import it from a library and not code that we edit usually.
If foo
is defined with const
then we wouldn’t be able to reassign it to a new value after we export it.
In Modules With a Single Export, Prefer Default Export Over Named Export
If we have only one member to export in a module, then we should just export it as a default export.
The advantage of that is that we can name it whatever we want when we import it without the as
keyword.
For instance, instead of writing the following:
module.js
export const bar = 2;
index.js
import { bar as foo } from "./module";
console.log(foo);
We can write the following instead:
module.js
const bar = 2;
export default bar;
index.js
import foo from "./module";
console.log(foo);
In the second example, we exported bar
as a default export. Then in index.js
, we can import it with the name foo
or any name instead of the name bar
without using the as
keyword.
This is convenient to avoid name clashes with imports if we have lots of imports.
Put All Import
s Above Non-Import Statements
Imports should be above non-import statements since this makes everyone clear where the exports are.
For instance, instead of writing the following code:
bar.js
export const bar = 1;
module.js
export const foo = 2;
index.js
import { foo } from "./module";
console.log(foo);
import { bar } from "./bar";
console.log(bar);
In the code above, we imported items from 2 different modules in index.js
and the 2 import statements are far from each other.
We have the foo
import separated from the bar
import with a console.log
call.
Import statements being scattered makes finding them hard.
Instead, we should write the following:
import { foo } from "./module";
import { bar } from "./bar";
console.log(foo, bar);
to group all the import
statements at the top of our code file.
Conclusion
We should group import statements together so that they can easily find the imports from the top of the code.
If we have only one member to export, then we can just export it as a default.
Also, we should export mutable bindings since they can be changed after they’re exported.